package com.atguigu.gulimall;

import org.mybatis.spring.annotation.MapperScan;
import org.redisson.spring.session.config.EnableRedissonHttpSession;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

/**
 * 1.整合mybatis-plus
 * 		1),导入依赖
 * 		<dependency>
 *         <groupId>com.baomidou</groupId>
 *         <artifactId>mybatis-plus-boot-starter</artifactId>
 *         <version>3.2.0</version>
 *     </dependency>
 *      2）,配置
 *      	1.配置数据源（写在common当中）
 *      		1.导入数据库驱动mysql connector (记得查看数据库版本)
 *      		2.在application.yml配置数据源相关信息
 *      	2.配置mybatis-plus
 *      		1. 使用@MapperScan
 *      		2. 告诉mybatis-plus  sql映射文件的位置
 * 2.逻辑删除
 *  1）配置全局逻辑删除规则  (可以省略)
 *  2）如果是低版本的mybatis-plus还需要配置逻辑删除的组件 。即配置bean(高版本可以省略)
 *  3）在指定表的指定字段加上逻辑注解：@TableLogic
 *
 *
 * 3.关于文件上传步骤：
 *  1）.引入starter：spring-cloud-starter-alicloud-oss
 *  2）.在配置文件里配置key,endpoint的信息即可
 *  3.）使用OSSClient进行相关操作
 *  *
 *  * 而我们使用的方式：
 *  * 服务端签名后直传
 *
 * 4.JSR303
 *  1）给Bean添加校验注解：javax.validation.constraints  并定义自己的message提示
 *  2）开启校验功能  @Valid
 *         效果：校验错误后会有默认的响应
 *  3）给校验的bean后紧跟一个BindingResult就可以获取到校验的结果
 *
 * 5.在后端数据校验环节做统一的异常处理(注意这是与服务降级的异常不同的场景)
 *   使用SpringMVC提供的@ControllerAdvice功能
 *   1）抽取一个异常处理类
 *
 * 6.分组校验(场景：新增品牌的校验和修改品牌的校验是不一样的)
 *    1）给校验注解标注什么情况需要进行校验：groups = {UpdateGroup.class}
 *    2）在对应Controlller的方法参数中的@Valid注解改为spring提供的校验注解，并添加上分组场景：@Validated(AddGroup.class)
 *    3）重新启动商品服务
 *    注意：没有标注分组的字段校验是失效的,只有不标注分组才生效，比如只写@Validated或@Valid
 *
 * 7.自定义校验注解(比如品牌的显示状态只能是0或1)
 *    1）编写一个自定义校验注解
 *    2）每个自定义校验注解关联一个自定义校验器，因此我们需要编写自定义校验器
 *    3）关联自定义校验注解和自定义校验器
 *    注意:一个校验注解可以由多个校验器同时工作,他会自动适配这个注解被被标注在了哪个类型属性上
 *    @Constraint(validatedBy = {ListValueConstraintValidator.class,.....可以使用不同的校验器进行校验 })
 *
 *
 * ---------------------------前端开发阶段-----------------------------------------
 * 8.引入模板引擎
 *     1）导入依赖：spring-boot-starter-thymeleaf，   并关闭thymeleaf的缓存
 *     2）只要按照要求：静态资源都放在static文件夹下，我们就可以按照路径直接访问
 *     3）页面放在templates下也可以直接访问
 *          springboot项目中,在访问项目的时候，默认会访问index.html页面
 *     4)页面修改在不重启服务器的情况下实时更新：但如果是代码配置，推荐还是重启
 *          4.1）引入：dev-tools
 *          4.2）重新构建项目或重新编译当前页面
 *
 * 9.整合redis
 *      1）引入data-redis-starter
 *      2)在application.yml中简单配置host等信息
 *      3）使用springboot自动配置的StringRedisTemplate<String,String>来操作redis
 *
 * 10.导入redisson作为分布式锁的框架
 *      1）引入依赖：
 *      <dependency>
 * 			<groupId>org.redisson</groupId>
 * 			<artifactId>redisson</artifactId>
 * 			<version>3.12.5</version>
 * 		</dependency>
 * 	    2）配置redisson
 *
 * 11.整合SpringCache简化缓存开发(如何简化？因为之前在用缓存的时候，哪个方法用缓存，哪个就需要写完整的读写缓存
 * 的逻辑，一样的操作我们反复写，很是麻烦，现在有一个组件：SpringCache可以让我们只添加注解就完成一整套的逻辑，
 * 岂不美哉？)
 *      1）引入依赖：
 *          <dependency>
 * 			    <groupId>org.springframework.boot</groupId>
 * 			    <artifactId>spring-boot-starter-cache</artifactId>
 * 			    <version>2.2.2.RELEASE</version>
 * 		    </dependency>
 *      2）注意，SpringCache只是一个缓存管理组件，真正做缓存的有很多实现，既可以用
 *          redis做缓存，也可以用ConcurrentHashMap做缓存，在我们的项目里，因为是
 *          分布式项目，我们因此选择redis作为缓存，我们在之前也引入了redis的依赖
 *      3）写配置
 *          我们先要搞懂SpringBoot自动帮我们配置了哪些SpringCache的配置呢？
 *          在类CacheAutoConfiguration中会导入RedisCacheConfiguration
 *          而我们来到RedisCacheConfiguration类中会发现它为我们注册了RedisCacheManager(缓存管理器)，
 *          这就是SpringCache用来管理缓存的CacheManager的redis版本的实现类.
 *          另外RedisCacheConfiguration会读取配置文件中我们的配置，并创建配置类(createConfiguration方法)
 *
 *          我们需要配置哪些内容呢？只需要配置使用redis作为缓存(在配置文件application.properties中)
 *      4）测试使用缓存
 *      @Cacheable: Triggers cache population.触发将数据保存到缓存的操作
 *      @CacheEvict: Triggers cache eviction. 触发将数据从缓存删除的操作
 *      @CachePut: Updates the cache without interfering with the method execution.不影响方法执行的方式更新缓存
 *      @Caching: Regroups multiple cache operations to be applied on a method. 组合以上多个操作
 *      @CacheConfig: Shares some common cache-related settings at class-level.在同一个类(类级别)共享缓存的相同配置
 *
 *          步骤：
 *          a）开启缓存功能：在主启动类添加注解：@EnableCaching
 *          b）只需要使用注解就能完成缓存操作:@Cacheable.每一个需要缓存的数据我们都来指定是要放到哪个名字的缓存{相当于给缓存分区(推荐按照业务类型进行分区)}
 *
 *          添加@Cacheable({"name"}) 注解后的默认行为：
 *          1.如果缓存中有，方法不调用
 *          2.key默认自动生成，名字为：  name::SimpleKey[]
 *          3.缓存的value的值，默认使用JDK序列化机制，将序列化后的数据存入redis
 *          4.默认ttl为永不过期，但却不符合我们之前的缓存设计规范
 *
 *          在默认行为的基础上，我们希望有一些自定义操作：都是@Cacheable注解中的设置
 *          1.指定生成缓存使用的key：   利用key属性指定，可以接受SpEL表达式（注意，如果是字符串的话必须添加单引号：key = "'levelFirstCategories'"）
 *              SpEL的详细语法参照：https://docs.spring.io/spring-framework/docs/5.1.12.RELEASE/spring-framework-reference/integration.html#cache-spel-context
 *          2.指定缓存的数据的存活时间 ：  在配置文件中修改ttl：spring.cache.redis.time-to-live=3600000
 *          3.由于使用JDK自带的序列化机制，在异构系统(比如PHP业务)也想获取缓存中的数据不兼容。我们希望将数据保存为json格式。
 *              这就涉及到修改缓存管理器的一些功能设置。下面详细说明设置原理
 *
 *              CacheAutoConfiguration帮助我们导入了RedisCacheConfiguration，而RedisCacheConfiguration
 *              帮助我们自动配置了缓存管理器RedisCacheManager。而RedisCacheManager会初始化所有的缓存，即按照我们在
 *              配置文件配置的缓存的名字来初始化，但在初始化缓存的时候最重要的是决定要用哪种缓存的配置(即调用determineConfiguration
 *              方法:如果redisCacheConfiguration有就用已有的，没有的话就用默认配置，即该方法的返回：redisCacheConfiguration.getIfAvailable(()->...))。
 *              我们如果想更改缓存的配置，只需要给容器中放入一个redisCacheConfiguration即可，
 *              这样的话，这个容器中的redisCacheConfiguration就会应用到当前RedisCacheManager管理的所有缓存“分区”中.
 *
 *              回过头来，关键的方法就是
 *              org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration类下的方法：determineConfiguration
 *              它来决定用哪个配置类redisCacheConfiguration，我们只需要研究此方法即可：
 *
 *              private org.springframework.data.redis.cache.RedisCacheConfiguration determineConfiguration(
 * 			    CacheProperties cacheProperties,
 * 			    ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration,
 * 			    ClassLoader classLoader) {
 * 		        return redisCacheConfiguration.getIfAvailable(() -> createConfiguration(cacheProperties, classLoader));
 *              }
 *
 *              我们看到该方法接受参数redisCacheConfiguration，这就是容器给该方法注入的实例，而它的核心就是：
 *              org.springframework.data.redis.cache.RedisCacheConfiguration。而这个类与上面的同名类所在的包不一样
 *              点进去我们就会发现该类就是关于redis缓存配置的核心内容！！！！！！！！！！！！！
 *
 *              下面就是更改序列化机制的步骤：
 *              1.创建自己的配置类：com.atguigu.gulimall.product.config.MyCacheConfig
 *              2.将主启动类的@EnableCaching移动到MyCacheConfig中
 *              3.参加具体的类是怎么写的( ▼-▼ )
 *
 *      5、Spring-Cache的不足；
 *          1）、读模式：
 *              缓存穿透：查询一个null数据。解决：缓存空数据；ache-null-values=true
 *              缓存击穿：大量并发进来同时查询一个正好过期的数据。解决：加锁；？默认是无加锁的;sync = true（加锁，解决击穿）
 *              缓存雪崩：大量的key同时过期。解决：加随机时间。加上过期时间。：spring.cache.redis.time-to-live=3600000
 *          2）、写模式：（缓存与数据库一致）
 *              1）、读写加锁。
 *              2）、引入Canal，感知到MySQL的更新去更新数据库
 *              3）、读多写多，直接去数据库查询就行
 *      总结：
 *          常规数据（读多写少，即时性，一致性要求不高的数据）；完全可以使用Spring-Cache；写模式（只要缓存的数据有过期时间就足够了）
 *          特殊数据：特殊设计
 */


@EnableRedisHttpSession
@MapperScan("com.atguigu.gulimall.product.dao")
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.atguigu.gulimall.product.feign")
public class GulimallProductApplication {

    public static void main(String[] args) {
        SpringApplication.run(GulimallProductApplication.class, args);
    }

}